Q1
First I load the data files
library(tidyverse)
dat <- read.csv("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/migration2012.csv")
states <- read.csv("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/states_chord.csv")
dat <- dat %>%
tidyr::gather(key=From, value=value, Alabama,Alaska,Arizona,Arkansas,California,Colorado,Connecticut,Delaware,District.of.Columbia,Florida,Georgia,Hawaii,Idaho,Illinois,Indiana,Iowa,Kansas,Kentucky,Louisiana,Maine,Maryland,Massachusetts,Michigan,Minnesota,Mississippi,Missouri,Montana,Nebraska,Nevada,New.Hampshire,New.Jersey,New.Mexico,New.York,North.Carolina,North.Dakota,Ohio,Oklahoma,Oregon,Pennsylvania,Rhode.Island,South.Carolina,South.Dakota,Tennessee,Texas,Utah,Vermont,Virginia,Washington,West.Virginia,Wisconsin,Wyoming)
Delete the puctuations.
dat <- data.frame(apply(dat, 2, function(y) gsub("[[:punct:]]", " ", y)))
dat
Then I add two new columns to the above data frame.
d1 <- merge(dat, states, by.x="From", by.y="States")
d1 <- d1[,c(-5,-6)]
names(d1)[4] <- "ID_From"
d2 <- merge(d1, states, by.x="To", by.y="States")
d2 <- d2[,c(-6,-7)]
names(d2)[5] <- "ID_To"
dat <- d2
dat
Sort the data frame in the ascending order
dat <- arrange(dat, ID_From)
dat <- arrange(dat, ID_To)
dat
Convert the above data frame into the matrix form
t1 <- dat[,c(1,2,4,5)]
t1 <- t1 %>%
spread(key=To, value=ID_To)
t2 <- dat[,c(1,2,3,4)]
t2 <- t2 %>%
spread(key=To, value=value)
t3 <- rbind(t1[1,],t2)
t3$From <- as.character(t3$From)
t3[1,2] <- 0
t3[1,1] <- "ID_To"
t3 <- t3 %>%
arrange(ID_From)
library(data.table)
data.table 1.10.4
**********
This installation of data.table has not detected OpenMP support. It will still work but in single-threaded mode. If this a Mac and you obtained the Mac binary of data.table from CRAN, CRAN's Mac does not yet support OpenMP. In the meantime please follow our Mac installation instructions on the data.table homepage. If it works and you observe benefits from multiple threads as others have reported, please convince Simon Ubanek by sending him evidence and ask him to turn on OpenMP support when CRAN builds package binaries for Mac. Alternatives are to install Ubuntu on your Mac (which I have done and works well) or use Windows where OpenMP is supported and works well.
**********
The fastest way to learn (by data.table authors): https://www.datacamp.com/courses/data-analysis-the-data-table-way
Documentation: ?data.table, example(data.table) and browseVignettes("data.table")
Release notes, videos and slides: http://r-datatable.com
-----------------------------------------------------------------------------------------------------------------------------------
data.table + dplyr code now lives in dtplyr.
Please library(dtplyr)!
-----------------------------------------------------------------------------------------------------------------------------------
次のパッケージを付け加えます: ‘data.table’
以下のオブジェクトは ‘package:dplyr’ からマスクされています:
between, first, last
以下のオブジェクトは ‘package:purrr’ からマスクされています:
transpose
setcolorder(t3,c("From","ID_From","Connecticut","Maine","Massachusetts","New Hampshire","Rhode Island",
"Vermont","New Jersey","New York","Pennsylvania","Illinois","Indiana",
"Michigan","Ohio","Wisconsin","Iowa","Kansas","Minnesota",
"Missouri","Nebraska","North Dakota","South Dakota","Delaware","Florida",
"Georgia","Maryland","North Carolina","South Carolina","Virginia","District of Columbia",
"West Virginia","Alabama","Kentucky","Mississippi","Tennessee","Arkansas",
"Louisiana","Oklahoma","Texas","Arizona","Colorado","Idaho",
"Montana","Nevada","New Mexico","Utah","Wyoming","Alaska",
"California","Hawaii","Oregon","Washington"))
t3 <- t3[c(-1),c(-2)]
t4 <- t3[,-1]
rownames(t4) <- t3[,1]
t5 <- data.frame(apply(t4, 2, function(y) as.numeric(y)))
rownames(t5) <- t3[,1]
t5
Drawing the chord diagram
chorddiag(as.matrix(t5),groupColors=states$Color,showTicks=F,groupnamePadding = 20,groupThickness=.05,groupnameFontsize=10)
row names of the 'data' matrix differ from its column names or the 'groupNames' argument.
Q2
Read “Stops On Lines” and the all GIS data of bus lines.
library(dplyr)
library(sp)
library(rgdal)
library(leaflet)
library(ggmap)
# Bus Stops
SOL <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/BusStops1216","StopsOnLines1216")
SOL.pj <- spTransform(SOL, CRS("+proj=longlat +datum=WGS84"))
# Bus Routes
CC <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/ComCir1216","ComCir1216")
CC.pj <- spTransform(CC, CRS("+proj=longlat +datum=WGS84"))
LE <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/LimExp1216","LimExp1216")
LE.pj <- spTransform(LE, CRS("+proj=longlat +datum=WGS84"))
LCBD <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/LocalCBD1216","LocalCBD1216")
LCBD.pj <- spTransform(LCBD, CRS("+proj=longlat +datum=WGS84"))
LNCBD <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/LocalNonCBD1216","LocalNonCBD1216")
LNCBD.pj <- spTransform(LNCBD, CRS("+proj=longlat +datum=WGS84"))
RBRT <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/RapidBRT1216","RapidBRT1216")
RBRT.pj <- spTransform(RBRT, CRS("+proj=longlat +datum=WGS84"))
tmp_CC <- geometry(CC.pj)
tmp_LE <- geometry(LE.pj)
tmp_LCBD <- geometry(LCBD.pj)
tmp_LNCBD <- geometry(LNCBD.pj)
tmp_RBRT <- geometry(RBRT.pj)
# Individual Bus Route
pjs <- list()
tmps <- list()
layer_list <- ogrListLayers("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/Individuals1216")
for (i in layer_list){
if (i != 728){
a <- spTransform(readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/Individuals1216", toString(i)), CRS("+proj=longlat +datum=WGS84"))
pjs <- c(pjs, a)
tmps <- c(tmps, geometry(a))
}
}
make Line_list
tmps <- c(tmps, tmp_CC, tmp_LE, tmp_LCBD, tmp_LNCBD, tmp_RBRT)
Line_list <- list()
for (i in 1:length(tmps)){
for (j in 1:length(tmps[[i]])){
Line_list <- c(Line_list, tmps[[i]][j]@lines[[1]]@Lines)
}
}
make new_id
pjs <- c(pjs, CC.pj, LE.pj, LCBD.pj, LNCBD.pj, RBRT.pj)
LinLSs <- list()
for (i in 1:length(pjs)){
LinLSs <- c(LinLSs, sapply(pjs[[i]]@lines, function(x) length(x@Lines)))
}
LinLSs <- LinLSs %>% unlist()
new_id <- sapply(1:length(LinLSs), function(x) paste0(x, "_", seq.int(LinLSs[[x]]))) %>%
unlist()
make data frame
## make a new data.frame (only route_id)
DAT=data.frame(matrix(rep(NA,1),nrow=1))[-1,]
for (i in 1:length(pjs)){
df <- data.frame(route_id = pjs[[i]]@data$VAR_IDENT)
DAT <- rbind(DAT, df)
}
rownames(DAT) <- new_id
SLDF <- mapply(function(x, y) Lines(x, ID = y), x = Line_list, y = new_id) %>%
#list() %>%
SpatialLines() %>%
SpatialLinesDataFrame(data = DAT)
make new lines and LA map
dat <- geocode('Los Angels')
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Los%20Angels&sensor=false
leaflet() %>%
setView(lng = dat['lon'], lat = dat['lat'] , zoom = 11) %>%
addPolylines(data = SLDF, color = "black", opacity = 1, weight = 1) %>%
addCircles(data=SOL.pj@data,~LONG, ~LAT, color = "red", weight = 0.3) %>%
addTiles()
Q3
First I load the data.
library(quantmod)
library(highcharter)
x <- getSymbols("AUD/JPY", src = "oanda", auto.assign = FALSE)
y <- getSymbols("GBP/USD", src = "oanda", auto.assign = FALSE)
Next make Bollinger’s bands for each exchange rate.
x.BBands.ll <- BBands(x)$dn
x.BBands.ul <- BBands(x)$up
x.BBands.m <- BBands(x)$mavg
y.BBands.ll <- BBands(y)$dn
y.BBands.ul <- BBands(y)$up
y.BBands.m <- BBands(y)$mavg
The drawing code is as follows.
hc <- highchart(type="stock") %>%
hc_title(text="Charting Exchange Rates") %>%
hc_subtitle(text = "Data extracted using quantmod package") %>%
hc_yAxis_multiples(
list(top = "0%", height = "50%", offset=0, opposite=TRUE),
list(top = "50%", height = "50%", offset=0, opposite=TRUE)
)%>%
hc_add_series(x, id = "audjpy",name ="audjpy", yAxis=0, color="blue", lineWidth=1.5) %>%
hc_add_series(x.BBands.ll, id = "audjpy.ll", name="audjpy Lower BBands",yAxis=0,
color="black",dashStyle='shortdash', lineWidth=1) %>%
hc_add_series(x.BBands.ul, id = "audjpy.ul", name="audjpy Upper BBands",yAxis=0,
color="black",lineWidth=1) %>%
hc_add_series(x.BBands.m, id = "audjpy.m",name="audjpy BBands MA", yAxis=0,
color="red",lineWidth=1) %>%
hc_add_series(y, id = "gbpusd",name="gbpusd",yAxis=1, color="green", lineWidth=1.5) %>%
hc_add_series(y.BBands.ll, id = "gbpusd.ll",name="gbpusd Lower BBands", yAxis=1,
color="black",dashStyle='shortdash',lineWidth=1) %>%
hc_add_series(y.BBands.ul, id = "gbpusd.ul",name="gbpusd Upper BBands", yAxis=1,
color="black",lineWidth=1) %>%
hc_add_series(y.BBands.m, id = "gbpusd.m",name="gbpusd BBands MA", yAxis=1,
color="red",lineWidth=1) %>%
hc_add_theme(hc_theme_538())
hc
Q4
Load libraries and check the raw data. And make ffdf after converting character columns to factor columns in original df.
library(nycflights13)
library(ffbase)
library(ffbase2)
library(biglm)
library(pROC)
library(chron)
tmp <- flights
tmp$carrier <- as.factor(tmp$carrier)
tmp$tailnum <- as.factor(tmp$tailnum)
tmp$origin <- as.factor(tmp$origin)
tmp$dest <- as.factor(tmp$dest)
flightff <- as.ffdf(tmp)
Next I make new columns as follows
flightff$Delay <- ffifelse(flightff$dep_delay > 0 | flightff$dep_delay == 0 , 1,0)
flightff$DepHour <- flightff$hour
flightff$Car <- ffifelse(flightff$carrier %in% as.factor(c("DL","US","DH","UA")), 1, 0)
flightff$Night <- ffifelse(flightff$hour > 18 | flightff$hour < 6, 1, 0)
flightff$Weekend <- ffifelse(day.of.week(month=flightff$month, day=flightff$day, year=flightff$year) == 6, 1, 0)
I exclude the rows whose Delay values are NA and rename it to logitff. And then I split the dataset into train set and test set.
logitff <- flightff[!is.na(flightff$Delay),]
indx <- ff(1:nrow(logitff))
p <- 0.7
trainIndx <- ff(indx[1:trunc(length(indx)*p)])
trainset <- logitff[trainIndx,]
testIndx <- ff(indx[(trunc(length(indx)*p)+1):length(indx)])
testset <- logitff[testIndx,]
Logistic regression
fit <- bigglm.ffdf(Delay~DepHour+Car+Night+Weekend, data = trainset, family=binomial(), sandwich=TRUE)
summary(fit)
Large data regression model: bigglm(Delay ~ DepHour + Car + Night + Weekend, data = trainset,
family = binomial(), sandwich = TRUE)
Sample size = 229964
Coef (95% CI) SE p
(Intercept) -1.5418 -1.5731 -1.5104 0.0157 0
DepHour 0.1020 0.0996 0.1044 0.0012 0
Car -0.0680 -0.0858 -0.0503 0.0089 0
Night -0.2619 -0.2912 -0.2326 0.0147 0
Weekend -0.1543 -0.1821 -0.1265 0.0139 0
Sandwich (model-robust) standard errors
predict and make confusionmatrix in train_set
train_pred <- predict(fit, newdata = trainset, type="response")
train_pred <- ifelse(train_pred>0.5, 1,0)
train_confusion <- table(as.integer(as.data.frame(trainset)$Delay), as.integer(train_pred))
train_confusion <- addmargins(train_confusion)
train_confusion
0 1 Sum
0 94128 36392 130520
1 56845 42599 99444
Sum 150973 78991 229964
predict and make confusionmatrix in test_set
test_pred <- predict(fit, newdata = testset, type="response")
test_pred <- ifelse(test_pred>0.5, 1,0)
test_confusion <- table(as.integer(as.data.frame(testset)$Delay), as.integer(test_pred))
test_confusion <- addmargins(test_confusion)
test_confusion
0 1 Sum
0 39997 13058 53055
1 25558 19944 45502
Sum 65555 33002 98557
Draw ROC curve
test_pred <- predict(fit, newdata = testset, type="response")
roc <- roc(as.integer(as.data.frame(testset)$Delay), as.numeric(test_pred))
plot(roc)

Q5
First I load the data. And before using spark I delete the irrelevant columns.
Remove the observations satisfying the condition
Split this data into trainset and testset.
test
$test
NA
Use Decision tree
decision_tree <- train %>%
ml_decision_tree(response="BOROUGH", features = c("LATITUDE","LONGITUDE"), max.bins = 200L, max.depth = 10L, seed=123L) %>%
Prediction
table(pred$BOROUGH, pred$prediction)
0 1 2 3 4
BRONX 0 0 0 8876 0
BROOKLYN 21181 0 24 0 1
MANHATTAN 0 17944 0 0 0
QUEENS 25 1 17777 0 0
STATEN ISLAND 23 0 0 0 3169
LS0tCnRpdGxlOiAiQmlnIERhdGEgQW5hbHl0aWNzIEFzc2lnbm1lbnQgMSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0Ci0tLQoKIyMgUTEKCkZpcnN0IEkgbG9hZCB0aGUgZGF0YSBmaWxlcwpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmRhdCA8LSByZWFkLmNzdigiL1VzZXJzL3N1c3UvRGVza3RvcC9Ib25nXCBLb25nL1NlbWVzdGVyMi9CaWdfRGF0YS9hc3NpZ25tZW50X2RhdGEvYXMxL21pZ3JhdGlvbjIwMTIuY3N2IikKc3RhdGVzIDwtIHJlYWQuY3N2KCIvVXNlcnMvc3VzdS9EZXNrdG9wL0hvbmdcIEtvbmcvU2VtZXN0ZXIyL0JpZ19EYXRhL2Fzc2lnbm1lbnRfZGF0YS9hczEvc3RhdGVzX2Nob3JkLmNzdiIpCgpkYXQgPC0gZGF0ICU+JQp0aWR5cjo6Z2F0aGVyKGtleT1Gcm9tLCB2YWx1ZT12YWx1ZSwgQWxhYmFtYSxBbGFza2EsQXJpem9uYSxBcmthbnNhcyxDYWxpZm9ybmlhLENvbG9yYWRvLENvbm5lY3RpY3V0LERlbGF3YXJlLERpc3RyaWN0Lm9mLkNvbHVtYmlhLEZsb3JpZGEsR2VvcmdpYSxIYXdhaWksSWRhaG8sSWxsaW5vaXMsSW5kaWFuYSxJb3dhLEthbnNhcyxLZW50dWNreSxMb3Vpc2lhbmEsTWFpbmUsTWFyeWxhbmQsTWFzc2FjaHVzZXR0cyxNaWNoaWdhbixNaW5uZXNvdGEsTWlzc2lzc2lwcGksTWlzc291cmksTW9udGFuYSxOZWJyYXNrYSxOZXZhZGEsTmV3LkhhbXBzaGlyZSxOZXcuSmVyc2V5LE5ldy5NZXhpY28sTmV3LllvcmssTm9ydGguQ2Fyb2xpbmEsTm9ydGguRGFrb3RhLE9oaW8sT2tsYWhvbWEsT3JlZ29uLFBlbm5zeWx2YW5pYSxSaG9kZS5Jc2xhbmQsU291dGguQ2Fyb2xpbmEsU291dGguRGFrb3RhLFRlbm5lc3NlZSxUZXhhcyxVdGFoLFZlcm1vbnQsVmlyZ2luaWEsV2FzaGluZ3RvbixXZXN0LlZpcmdpbmlhLFdpc2NvbnNpbixXeW9taW5nKQpgYGAKCkRlbGV0ZSB0aGUgcHVjdHVhdGlvbnMuCmBgYHtyfQpkYXQgPC0gZGF0YS5mcmFtZShhcHBseShkYXQsIDIsIGZ1bmN0aW9uKHkpIGdzdWIoIltbOnB1bmN0Ol1dIiwgIiAiLCB5KSkpCmRhdApgYGAKClRoZW4gSSBhZGQgdHdvIG5ldyBjb2x1bW5zIHRvIHRoZSBhYm92ZSBkYXRhIGZyYW1lLgoKYGBge3J9CmQxIDwtIG1lcmdlKGRhdCwgc3RhdGVzLCBieS54PSJGcm9tIiwgYnkueT0iU3RhdGVzIikKZDEgPC0gZDFbLGMoLTUsLTYpXQpuYW1lcyhkMSlbNF0gPC0gIklEX0Zyb20iCgpkMiA8LSBtZXJnZShkMSwgc3RhdGVzLCBieS54PSJUbyIsIGJ5Lnk9IlN0YXRlcyIpCmQyIDwtIGQyWyxjKC02LC03KV0KbmFtZXMoZDIpWzVdIDwtICJJRF9UbyIKZGF0IDwtIGQyCmRhdApgYGAKClNvcnQgdGhlIGRhdGEgZnJhbWUgaW4gdGhlIGFzY2VuZGluZyBvcmRlcgoKYGBge3J9CmRhdCA8LSBhcnJhbmdlKGRhdCwgSURfRnJvbSkKZGF0IDwtIGFycmFuZ2UoZGF0LCBJRF9UbykKZGF0CmBgYAoKQ29udmVydCB0aGUgYWJvdmUgZGF0YSBmcmFtZSBpbnRvIHRoZSBtYXRyaXggZm9ybQoKYGBge3J9CnQxIDwtIGRhdFssYygxLDIsNCw1KV0KdDEgPC0gdDEgJT4lCiAgc3ByZWFkKGtleT1UbywgdmFsdWU9SURfVG8pCgp0MiA8LSBkYXRbLGMoMSwyLDMsNCldCnQyIDwtIHQyICU+JQogIHNwcmVhZChrZXk9VG8sIHZhbHVlPXZhbHVlKQoKdDMgPC0gcmJpbmQodDFbMSxdLHQyKQp0MyRGcm9tIDwtIGFzLmNoYXJhY3Rlcih0MyRGcm9tKQp0M1sxLDJdIDwtIDAKdDNbMSwxXSA8LSAiSURfVG8iCnQzIDwtIHQzICU+JQogIGFycmFuZ2UoSURfRnJvbSkKCmxpYnJhcnkoZGF0YS50YWJsZSkKc2V0Y29sb3JkZXIodDMsYygiRnJvbSIsIklEX0Zyb20iLCJDb25uZWN0aWN1dCIsIk1haW5lIiwiTWFzc2FjaHVzZXR0cyIsIk5ldyBIYW1wc2hpcmUiLCJSaG9kZSBJc2xhbmQiLAoiVmVybW9udCIsIk5ldyBKZXJzZXkiLCJOZXcgWW9yayIsIlBlbm5zeWx2YW5pYSIsIklsbGlub2lzIiwiSW5kaWFuYSIsCiJNaWNoaWdhbiIsIk9oaW8iLCJXaXNjb25zaW4iLCJJb3dhIiwiS2Fuc2FzIiwiTWlubmVzb3RhIiwKIk1pc3NvdXJpIiwiTmVicmFza2EiLCJOb3J0aCBEYWtvdGEiLCJTb3V0aCBEYWtvdGEiLCJEZWxhd2FyZSIsIkZsb3JpZGEiLAoiR2VvcmdpYSIsIk1hcnlsYW5kIiwiTm9ydGggQ2Fyb2xpbmEiLCJTb3V0aCBDYXJvbGluYSIsIlZpcmdpbmlhIiwiRGlzdHJpY3Qgb2YgQ29sdW1iaWEiLAoiV2VzdCBWaXJnaW5pYSIsIkFsYWJhbWEiLCJLZW50dWNreSIsIk1pc3Npc3NpcHBpIiwiVGVubmVzc2VlIiwiQXJrYW5zYXMiLAoiTG91aXNpYW5hIiwiT2tsYWhvbWEiLCJUZXhhcyIsIkFyaXpvbmEiLCJDb2xvcmFkbyIsIklkYWhvIiwKIk1vbnRhbmEiLCJOZXZhZGEiLCJOZXcgTWV4aWNvIiwiVXRhaCIsIld5b21pbmciLCJBbGFza2EiLAoiQ2FsaWZvcm5pYSIsIkhhd2FpaSIsIk9yZWdvbiIsIldhc2hpbmd0b24iKSkKCnQzIDwtIHQzW2MoLTEpLGMoLTIpXQoKdDQgPC0gdDNbLC0xXQpyb3duYW1lcyh0NCkgPC0gdDNbLDFdCgp0NSA8LSBkYXRhLmZyYW1lKGFwcGx5KHQ0LCAyLCBmdW5jdGlvbih5KSBhcy5udW1lcmljKHkpKSkKcm93bmFtZXModDUpIDwtIHQzWywxXQp0NQpgYGAKCkRyYXdpbmcgdGhlIGNob3JkIGRpYWdyYW0KYGBge3J9CmxpYnJhcnkoY2hvcmRkaWFnKQoKY2hvcmRkaWFnKGFzLm1hdHJpeCh0NSksZ3JvdXBDb2xvcnM9c3RhdGVzJENvbG9yLHNob3dUaWNrcz1GLGdyb3VwbmFtZVBhZGRpbmc9MjAsZ3JvdXBUaGlja25lc3M9LjA1LGdyb3VwbmFtZUZvbnRzaXplPTEwKQpgYGAKCgojIyBRMgoKUmVhZCAiU3RvcHMgT24gTGluZXMiIGFuZCB0aGUgYWxsIEdJUyBkYXRhIG9mIGJ1cyBsaW5lcy4KYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoc3ApCmxpYnJhcnkocmdkYWwpCmxpYnJhcnkobGVhZmxldCkKbGlicmFyeShnZ21hcCkKCiMgQnVzIFN0b3BzClNPTCA8LSByZWFkT0dSKCIvVXNlcnMvc3VzdS9EZXNrdG9wL0hvbmdcIEtvbmcvU2VtZXN0ZXIyL0JpZ19EYXRhL2Fzc2lnbm1lbnRfZGF0YS9hczEvQnVzU3RvcHMxMjE2IiwiU3RvcHNPbkxpbmVzMTIxNiIpClNPTC5waiA8LSBzcFRyYW5zZm9ybShTT0wsIENSUygiK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQiKSkKCiMgQnVzIFJvdXRlcwpDQyA8LSByZWFkT0dSKCIvVXNlcnMvc3VzdS9EZXNrdG9wL0hvbmdcIEtvbmcvU2VtZXN0ZXIyL0JpZ19EYXRhL2Fzc2lnbm1lbnRfZGF0YS9hczEvQ29tQ2lyMTIxNiIsIkNvbUNpcjEyMTYiKQpDQy5waiA8LSBzcFRyYW5zZm9ybShDQywgQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCIpKQpMRSA8LSByZWFkT0dSKCIvVXNlcnMvc3VzdS9EZXNrdG9wL0hvbmdcIEtvbmcvU2VtZXN0ZXIyL0JpZ19EYXRhL2Fzc2lnbm1lbnRfZGF0YS9hczEvTGltRXhwMTIxNiIsIkxpbUV4cDEyMTYiKQpMRS5waiA8LSBzcFRyYW5zZm9ybShMRSwgQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCIpKQpMQ0JEIDwtIHJlYWRPR1IoIi9Vc2Vycy9zdXN1L0Rlc2t0b3AvSG9uZ1wgS29uZy9TZW1lc3RlcjIvQmlnX0RhdGEvYXNzaWdubWVudF9kYXRhL2FzMS9Mb2NhbENCRDEyMTYiLCJMb2NhbENCRDEyMTYiKQpMQ0JELnBqIDwtIHNwVHJhbnNmb3JtKExDQkQsIENSUygiK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQiKSkKTE5DQkQgPC0gcmVhZE9HUigiL1VzZXJzL3N1c3UvRGVza3RvcC9Ib25nXCBLb25nL1NlbWVzdGVyMi9CaWdfRGF0YS9hc3NpZ25tZW50X2RhdGEvYXMxL0xvY2FsTm9uQ0JEMTIxNiIsIkxvY2FsTm9uQ0JEMTIxNiIpCkxOQ0JELnBqIDwtIHNwVHJhbnNmb3JtKExOQ0JELCBDUlMoIitwcm9qPWxvbmdsYXQgK2RhdHVtPVdHUzg0IikpClJCUlQgPC0gcmVhZE9HUigiL1VzZXJzL3N1c3UvRGVza3RvcC9Ib25nXCBLb25nL1NlbWVzdGVyMi9CaWdfRGF0YS9hc3NpZ25tZW50X2RhdGEvYXMxL1JhcGlkQlJUMTIxNiIsIlJhcGlkQlJUMTIxNiIpClJCUlQucGogPC0gc3BUcmFuc2Zvcm0oUkJSVCwgQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCIpKQoKdG1wX0NDIDwtIGdlb21ldHJ5KENDLnBqKQp0bXBfTEUgPC0gZ2VvbWV0cnkoTEUucGopCnRtcF9MQ0JEIDwtIGdlb21ldHJ5KExDQkQucGopCnRtcF9MTkNCRCA8LSBnZW9tZXRyeShMTkNCRC5waikKdG1wX1JCUlQgPC0gZ2VvbWV0cnkoUkJSVC5waikKCiMgSW5kaXZpZHVhbCBCdXMgUm91dGUKcGpzIDwtIGxpc3QoKQp0bXBzIDwtIGxpc3QoKQpsYXllcl9saXN0IDwtIG9nckxpc3RMYXllcnMoIi9Vc2Vycy9zdXN1L0Rlc2t0b3AvSG9uZ1wgS29uZy9TZW1lc3RlcjIvQmlnX0RhdGEvYXNzaWdubWVudF9kYXRhL2FzMS9JbmRpdmlkdWFsczEyMTYiKQpmb3IgKGkgaW4gbGF5ZXJfbGlzdCl7CiAgaWYgKGkgIT0gNzI4KXsKICAgIGEgPC0gc3BUcmFuc2Zvcm0ocmVhZE9HUigiL1VzZXJzL3N1c3UvRGVza3RvcC9Ib25nXCBLb25nL1NlbWVzdGVyMi9CaWdfRGF0YS9hc3NpZ25tZW50X2RhdGEvYXMxL0luZGl2aWR1YWxzMTIxNiIsIHRvU3RyaW5nKGkpKSwgQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCIpKQogICAgcGpzIDwtIGMocGpzLCBhKQogICAgdG1wcyA8LSBjKHRtcHMsIGdlb21ldHJ5KGEpKQogIH0KfQpgYGAKCm1ha2UgTGluZV9saXN0CmBgYHtyfQp0bXBzIDwtIGModG1wcywgdG1wX0NDLCB0bXBfTEUsIHRtcF9MQ0JELCB0bXBfTE5DQkQsIHRtcF9SQlJUKQoKTGluZV9saXN0IDwtIGxpc3QoKQpmb3IgKGkgaW4gMTpsZW5ndGgodG1wcykpewogIGZvciAoaiBpbiAxOmxlbmd0aCh0bXBzW1tpXV0pKXsKICAgIExpbmVfbGlzdCA8LSBjKExpbmVfbGlzdCwgdG1wc1tbaV1dW2pdQGxpbmVzW1sxXV1ATGluZXMpCiAgfQp9CmBgYAoKCm1ha2UgbmV3X2lkCmBgYHtyfQpwanMgPC0gYyhwanMsIENDLnBqLCBMRS5waiwgTENCRC5waiwgTE5DQkQucGosIFJCUlQucGopCkxpbkxTcyA8LSBsaXN0KCkKZm9yIChpIGluIDE6bGVuZ3RoKHBqcykpewogTGluTFNzIDwtIGMoTGluTFNzLCBzYXBwbHkocGpzW1tpXV1AbGluZXMsIGZ1bmN0aW9uKHgpIGxlbmd0aCh4QExpbmVzKSkpCn0KTGluTFNzIDwtIExpbkxTcyAlPiUgdW5saXN0KCkKCm5ld19pZCA8LSBzYXBwbHkoMTpsZW5ndGgoTGluTFNzKSwgZnVuY3Rpb24oeCkgcGFzdGUwKHgsICJfIiwgc2VxLmludChMaW5MU3NbW3hdXSkpKSAlPiUgCiAgdW5saXN0KCkKYGBgCgptYWtlIGRhdGEgZnJhbWUKYGBge3J9CiMjIG1ha2UgYSBuZXcgZGF0YS5mcmFtZSAob25seSByb3V0ZV9pZCkKREFUPWRhdGEuZnJhbWUobWF0cml4KHJlcChOQSwxKSxucm93PTEpKVstMSxdCmZvciAoaSBpbiAxOmxlbmd0aChwanMpKXsKICBkZiA8LSBkYXRhLmZyYW1lKHJvdXRlX2lkID0gcGpzW1tpXV1AZGF0YSRWQVJfSURFTlQpCiAgREFUIDwtIHJiaW5kKERBVCwgZGYpCn0Kcm93bmFtZXMoREFUKSA8LSBuZXdfaWQKClNMREYgPC0gbWFwcGx5KGZ1bmN0aW9uKHgsIHkpIExpbmVzKHgsIElEID0geSksIHggPSBMaW5lX2xpc3QsIHkgPSBuZXdfaWQpICU+JQogICNsaXN0KCkgJT4lCiAgU3BhdGlhbExpbmVzKCkgJT4lIAogIFNwYXRpYWxMaW5lc0RhdGFGcmFtZShkYXRhID0gREFUKQpgYGAKCgptYWtlIG5ldyBsaW5lcyBhbmQgTEEgbWFwCmBgYHtyfQpkYXQgPC0gZ2VvY29kZSgnTG9zIEFuZ2VscycpCgpsZWFmbGV0KCkgJT4lCiAgc2V0VmlldyhsbmcgPSBkYXRbJ2xvbiddLCBsYXQgPSBkYXRbJ2xhdCddCSwgem9vbSA9IDExKSAlPiUKICBhZGRQb2x5bGluZXMoZGF0YSA9IFNMREYsIGNvbG9yID0gImJsYWNrIiwgb3BhY2l0eSA9IDEsIHdlaWdodCA9IDEpICU+JSAKICBhZGRDaXJjbGVzKGRhdGE9U09MLnBqQGRhdGEsfkxPTkcsIH5MQVQsIGNvbG9yID0gInJlZCIsIHdlaWdodCA9IDAuMykgJT4lCiAgYWRkVGlsZXMoKQpgYGAKCgojIyBRMwoKRmlyc3QgSSBsb2FkIHRoZSBkYXRhLgpgYGB7cn0KbGlicmFyeShxdWFudG1vZCkKbGlicmFyeShoaWdoY2hhcnRlcikKCnggPC0gZ2V0U3ltYm9scygiQVVEL0pQWSIsIHNyYyA9ICJvYW5kYSIsIGF1dG8uYXNzaWduID0gRkFMU0UpCnkgPC0gZ2V0U3ltYm9scygiR0JQL1VTRCIsIHNyYyA9ICJvYW5kYSIsIGF1dG8uYXNzaWduID0gRkFMU0UpCmBgYAoKTmV4dCBtYWtlIEJvbGxpbmdlcidzIGJhbmRzIGZvciBlYWNoIGV4Y2hhbmdlIHJhdGUuCmBgYHtyfQp4LkJCYW5kcy5sbCA8LSBCQmFuZHMoeCkkZG4KeC5CQmFuZHMudWwgPC0gQkJhbmRzKHgpJHVwCnguQkJhbmRzLm0gPC0gQkJhbmRzKHgpJG1hdmcKeS5CQmFuZHMubGwgPC0gQkJhbmRzKHkpJGRuCnkuQkJhbmRzLnVsIDwtIEJCYW5kcyh5KSR1cAp5LkJCYW5kcy5tIDwtIEJCYW5kcyh5KSRtYXZnCmBgYAoKVGhlIGRyYXdpbmcgY29kZSBpcyBhcyBmb2xsb3dzLgpgYGB7cn0KaGMgPC0gaGlnaGNoYXJ0KHR5cGU9InN0b2NrIikgJT4lIAogIGhjX3RpdGxlKHRleHQ9IkNoYXJ0aW5nIEV4Y2hhbmdlIFJhdGVzIikgJT4lIAogIGhjX3N1YnRpdGxlKHRleHQgPSAiRGF0YSBleHRyYWN0ZWQgdXNpbmcgcXVhbnRtb2QgcGFja2FnZSIpICU+JSAKICBoY195QXhpc19tdWx0aXBsZXMoCiAgICBsaXN0KHRvcCA9ICIwJSIsIGhlaWdodCA9ICI1MCUiLCBvZmZzZXQ9MCwgb3Bwb3NpdGU9VFJVRSksCiAgICBsaXN0KHRvcCA9ICI1MCUiLCBoZWlnaHQgPSAiNTAlIiwgb2Zmc2V0PTAsIG9wcG9zaXRlPVRSVUUpCiAgKSU+JQogIGhjX2FkZF9zZXJpZXMoeCwgaWQgPSAiYXVkanB5IixuYW1lID0iYXVkanB5IiwgeUF4aXM9MCwgY29sb3I9ImJsdWUiLCBsaW5lV2lkdGg9MS41KSAlPiUKICBoY19hZGRfc2VyaWVzKHguQkJhbmRzLmxsLCBpZCA9ICJhdWRqcHkubGwiLCBuYW1lPSJhdWRqcHkgTG93ZXIgQkJhbmRzIix5QXhpcz0wLAogICAgICAgICAgICAgICAgY29sb3I9ImJsYWNrIixkYXNoU3R5bGU9J3Nob3J0ZGFzaCcsIGxpbmVXaWR0aD0xKSAlPiUKICBoY19hZGRfc2VyaWVzKHguQkJhbmRzLnVsLCBpZCA9ICJhdWRqcHkudWwiLCBuYW1lPSJhdWRqcHkgVXBwZXIgQkJhbmRzIix5QXhpcz0wLAogICAgICAgICAgICAgICAgY29sb3I9ImJsYWNrIixsaW5lV2lkdGg9MSkgJT4lCiAgaGNfYWRkX3Nlcmllcyh4LkJCYW5kcy5tLCBpZCA9ICJhdWRqcHkubSIsbmFtZT0iYXVkanB5IEJCYW5kcyBNQSIsIHlBeGlzPTAsCiAgICAgICAgICAgICAgICBjb2xvcj0icmVkIixsaW5lV2lkdGg9MSkgJT4lCiAgaGNfYWRkX3Nlcmllcyh5LCBpZCA9ICJnYnB1c2QiLG5hbWU9ImdicHVzZCIseUF4aXM9MSwgY29sb3I9ImdyZWVuIiwgbGluZVdpZHRoPTEuNSkgJT4lCiAgaGNfYWRkX3Nlcmllcyh5LkJCYW5kcy5sbCwgaWQgPSAiZ2JwdXNkLmxsIixuYW1lPSJnYnB1c2QgTG93ZXIgQkJhbmRzIiwgeUF4aXM9MSwKICAgICAgICAgICAgICAgIGNvbG9yPSJibGFjayIsZGFzaFN0eWxlPSdzaG9ydGRhc2gnLGxpbmVXaWR0aD0xKSAlPiUKICBoY19hZGRfc2VyaWVzKHkuQkJhbmRzLnVsLCBpZCA9ICJnYnB1c2QudWwiLG5hbWU9ImdicHVzZCBVcHBlciBCQmFuZHMiLCB5QXhpcz0xLAogICAgICAgICAgICAgICAgY29sb3I9ImJsYWNrIixsaW5lV2lkdGg9MSkgJT4lCiAgaGNfYWRkX3Nlcmllcyh5LkJCYW5kcy5tLCBpZCA9ICJnYnB1c2QubSIsbmFtZT0iZ2JwdXNkIEJCYW5kcyBNQSIsIHlBeGlzPTEsCiAgICAgICAgICAgICAgICBjb2xvcj0icmVkIixsaW5lV2lkdGg9MSkgJT4lCiAgaGNfYWRkX3RoZW1lKGhjX3RoZW1lXzUzOCgpKQoKaGMKYGBgCgoKIyMgUTQKCkxvYWQgbGlicmFyaWVzIGFuZCBjaGVjayB0aGUgcmF3IGRhdGEuIEFuZCBtYWtlIGZmZGYgYWZ0ZXIgY29udmVydGluZyBjaGFyYWN0ZXIgY29sdW1ucyB0byBmYWN0b3IgY29sdW1ucyBpbiBvcmlnaW5hbCBkZi4KYGBge3J9CmxpYnJhcnkobnljZmxpZ2h0czEzKQpsaWJyYXJ5KGZmYmFzZSkKbGlicmFyeShmZmJhc2UyKQpsaWJyYXJ5KGJpZ2xtKQpsaWJyYXJ5KHBST0MpCmxpYnJhcnkoY2hyb24pCgp0bXAgPC0gZmxpZ2h0cwp0bXAkY2FycmllciA8LSBhcy5mYWN0b3IodG1wJGNhcnJpZXIpCnRtcCR0YWlsbnVtIDwtIGFzLmZhY3Rvcih0bXAkdGFpbG51bSkKdG1wJG9yaWdpbiA8LSBhcy5mYWN0b3IodG1wJG9yaWdpbikKdG1wJGRlc3QgPC0gYXMuZmFjdG9yKHRtcCRkZXN0KQoKZmxpZ2h0ZmYgPC0gYXMuZmZkZih0bXApCmBgYAoKTmV4dCBJIG1ha2UgbmV3IGNvbHVtbnMgYXMgZm9sbG93cwpgYGB7cn0KZmxpZ2h0ZmYkRGVsYXkgPC0gZmZpZmVsc2UoZmxpZ2h0ZmYkZGVwX2RlbGF5ID4gMCB8IGZsaWdodGZmJGRlcF9kZWxheSA9PSAwICwgMSwwKQpmbGlnaHRmZiREZXBIb3VyIDwtIGZsaWdodGZmJGhvdXIKZmxpZ2h0ZmYkQ2FyIDwtIGZmaWZlbHNlKGZsaWdodGZmJGNhcnJpZXIgJWluJSBhcy5mYWN0b3IoYygiREwiLCJVUyIsIkRIIiwiVUEiKSksIDEsIDApCmZsaWdodGZmJE5pZ2h0IDwtIGZmaWZlbHNlKGZsaWdodGZmJGhvdXIgPiAxOCB8IGZsaWdodGZmJGhvdXIgPCA2LCAxLCAwKQpmbGlnaHRmZiRXZWVrZW5kIDwtIGZmaWZlbHNlKGRheS5vZi53ZWVrKG1vbnRoPWZsaWdodGZmJG1vbnRoLCBkYXk9ZmxpZ2h0ZmYkZGF5LCB5ZWFyPWZsaWdodGZmJHllYXIpID09IDYsIDEsIDApCmBgYAoKSSBleGNsdWRlIHRoZSByb3dzIHdob3NlIERlbGF5IHZhbHVlcyBhcmUgTkEgYW5kIHJlbmFtZSBpdCB0byBsb2dpdGZmLgpBbmQgdGhlbiBJIHNwbGl0IHRoZSBkYXRhc2V0IGludG8gdHJhaW4gc2V0IGFuZCB0ZXN0IHNldC4KYGBge3J9CmxvZ2l0ZmYgPC0gZmxpZ2h0ZmZbIWlzLm5hKGZsaWdodGZmJERlbGF5KSxdCgppbmR4IDwtIGZmKDE6bnJvdyhsb2dpdGZmKSkKcCA8LSAwLjcKdHJhaW5JbmR4IDwtIGZmKGluZHhbMTp0cnVuYyhsZW5ndGgoaW5keCkqcCldKQp0cmFpbnNldCA8LSBsb2dpdGZmW3RyYWluSW5keCxdCnRlc3RJbmR4IDwtIGZmKGluZHhbKHRydW5jKGxlbmd0aChpbmR4KSpwKSsxKTpsZW5ndGgoaW5keCldKQp0ZXN0c2V0IDwtIGxvZ2l0ZmZbdGVzdEluZHgsXQpgYGAKCkxvZ2lzdGljIHJlZ3Jlc3Npb24gCmBgYHtyfQpmaXQgPC0gYmlnZ2xtLmZmZGYoRGVsYXl+RGVwSG91citDYXIrTmlnaHQrV2Vla2VuZCwgZGF0YSA9IHRyYWluc2V0LCBmYW1pbHk9Ymlub21pYWwoKSwgc2FuZHdpY2g9VFJVRSkKc3VtbWFyeShmaXQpCmBgYAoKcHJlZGljdCBhbmQgbWFrZSBjb25mdXNpb25tYXRyaXggaW4gdHJhaW5fc2V0CmBgYHtyfQp0cmFpbl9wcmVkIDwtIHByZWRpY3QoZml0LCBuZXdkYXRhID0gdHJhaW5zZXQsIHR5cGU9InJlc3BvbnNlIikKdHJhaW5fcHJlZCA8LSBpZmVsc2UodHJhaW5fcHJlZD4wLjUsIDEsMCkKdHJhaW5fY29uZnVzaW9uIDwtIHRhYmxlKGFzLmludGVnZXIoYXMuZGF0YS5mcmFtZSh0cmFpbnNldCkkRGVsYXkpLCBhcy5pbnRlZ2VyKHRyYWluX3ByZWQpKQp0cmFpbl9jb25mdXNpb24gPC0gYWRkbWFyZ2lucyh0cmFpbl9jb25mdXNpb24pCnRyYWluX2NvbmZ1c2lvbgpgYGAKCnByZWRpY3QgYW5kIG1ha2UgY29uZnVzaW9ubWF0cml4IGluIHRlc3Rfc2V0CmBgYHtyfQp0ZXN0X3ByZWQgPC0gcHJlZGljdChmaXQsIG5ld2RhdGEgPSB0ZXN0c2V0LCB0eXBlPSJyZXNwb25zZSIpCnRlc3RfcHJlZCA8LSBpZmVsc2UodGVzdF9wcmVkPjAuNSwgMSwwKQp0ZXN0X2NvbmZ1c2lvbiA8LSB0YWJsZShhcy5pbnRlZ2VyKGFzLmRhdGEuZnJhbWUodGVzdHNldCkkRGVsYXkpLCBhcy5pbnRlZ2VyKHRlc3RfcHJlZCkpCnRlc3RfY29uZnVzaW9uIDwtIGFkZG1hcmdpbnModGVzdF9jb25mdXNpb24pCnRlc3RfY29uZnVzaW9uCmBgYAoKRHJhdyBST0MgY3VydmUKYGBge3J9CnRlc3RfcHJlZCA8LSBwcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IHRlc3RzZXQsIHR5cGU9InJlc3BvbnNlIikKcm9jIDwtIHJvYyhhcy5pbnRlZ2VyKGFzLmRhdGEuZnJhbWUodGVzdHNldCkkRGVsYXkpLCBhcy5udW1lcmljKHRlc3RfcHJlZCkpCnBsb3Qocm9jKQpgYGAKCgojIyBRNQoKRmlyc3QgSSBsb2FkIHRoZSBkYXRhLiBBbmQgYmVmb3JlIHVzaW5nIHNwYXJrIEkgZGVsZXRlIHRoZSBpcnJlbGV2YW50IGNvbHVtbnMuCmBgYHtyfQpsaWJyYXJ5KHNwYXJrbHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHJlYWRyKQpzYyA8LSBzcGFya19jb25uZWN0KG1hc3RlciA9ICJsb2NhbCIpCgojIOWFg+OBrmNzduOBq+WVj+mhjOOBjOOBguOCi+OBo+OBveOBhO+8nyDihpIgVW5zcGVjaWZpZWTjgYzmgJLjgonjgozjgabjgovjgaPjgb3jgYTjgIDihpLjgIBVbnNwZWNpZmllZOOCkjDjgavlpInjgYjjgovjgYvjgILihpLku4rluqbjga9QQVNTRU5HRVIgVkVISUNLReOBjOaAkuOCieOCjOOCi+OAggojIOimgeOBmeOCi+OBq1N0cmluZ+OBjOWFqOmDqOODgOODoeOBo+OBveOBhOOAguOBp+OCgnRpdGFuaWPjgafjga9TdHJpbmfjgoLkuIrmiYvjgY/muKHjgZvjgabjgovjgILjgarjgZzvvJ8KIyDkuozjgaTjga7kvovjgYvjgonnqbrnmb3jga/liKXjgavllY/poYzjgafjga/jgarjgYTjgZPjgajjgYzjgo/jgYvjgovjgIIKZGF0IDwtIHJlYWRfY3N2KCIvVXNlcnMvc3VzdS9EZXNrdG9wL0hvbmdcIEtvbmcvU2VtZXN0ZXIyL0JpZ19EYXRhL2Fzc2lnbm1lbnRfZGF0YS9hczEvTllQRF9Nb3Rvcl9WZWhpY2xlX0NvbGxpc2lvbnMuY3N2IikKZGF0IDwtIGRhdFssYygiQk9ST1VHSCIsIkxBVElUVURFIiwiTE9OR0lUVURFIiwiVU5JUVVFIEtFWSIpXQpueXBkX3RibCA8LSBjb3B5X3RvKHNjLCBkYXQsICJueXBkX3RibCIsb3ZlcndyaXRlID0gVFJVRSkKYGBgCgpSZW1vdmUgdGhlIG9ic2VydmF0aW9ucyBzYXRpc2Z5aW5nIHRoZSBjb25kaXRpb24KYGBge3J9Cm55cGRfdGJsIDwtIG55cGRfdGJsICU+JQogIGZpbHRlcihCT1JPVUdIIT0iIiwhaXMubmEoTEFUSVRVREUpLCFpcy5uYShMT05HSVRVREUpLExBVElUVURFIT0wLExPTkdJVFVERSE9MCkKbnlwZF90YmwKYGBgCgpTcGxpdCB0aGlzIGRhdGEgaW50byB0cmFpbnNldCBhbmQgdGVzdHNldC4KYGBge3J9CnBhcnRpdGlvbnMgPC0gbnlwZF90YmwgJT4lCiAgc2RmX3BhcnRpdGlvbih0cmFpbmluZyA9IDAuOSwgdGVzdCA9IDAuMSwgc2VlZCA9IDEyMykKdHJhaW4gPC0gcGFydGl0aW9uc1sxXSR0cmFpbmluZwp0ZXN0IDwtIHBhcnRpdGlvbnNbMl0kdGVzdApgYGAKClVzZSBEZWNpc2lvbiB0cmVlCmBgYHtyfQpkZWNpc2lvbl90cmVlIDwtIHRyYWluICU+JQogIG1sX2RlY2lzaW9uX3RyZWUocmVzcG9uc2U9IkJPUk9VR0giLCBmZWF0dXJlcyA9IGMoIkxBVElUVURFIiwiTE9OR0lUVURFIiksIG1heC5iaW5zID0gMjAwTCwgbWF4LmRlcHRoID0gMTBMLCBzZWVkPTEyM0wpICU+JQpgYGAKClByZWRpY3Rpb24KYGBge3J9CnByZWQgPC0gc2RmX3ByZWRpY3QoZGVjaXNpb25fdHJlZSwgdGVzdCkgJT4lCiAgY29sbGVjdAoKdGFibGUocHJlZCRCT1JPVUdILCBwcmVkJHByZWRpY3Rpb24pCmBgYAo=